/**
 * 
 */
package com.sfeir.client.tetriselement;

import static com.sfeir.client.util.Constants.SQUARE_SIZE;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Image;
import com.sfeir.client.GameManager.Sens;
import com.sfeir.client.Point;

/**
 * @author François LAROCHE
 */
public abstract class TetrisObject {

	private static final int TRANSLATION_PROTECTION_INTERVAL = 30;
	private static final double ANGLE = 0.5;

	protected int posX, posY;

	private Image brick;
	private double currentRotation;
	protected double rotation;
	private final List<boolean[][]> matrixes;
	private boolean isMoving = false;
	private boolean isMovingDown = false;
	

	public TetrisObject(Image brick, boolean [][] matrix1, boolean [][] matrix2, boolean [][] matrix3, boolean [][] matrix4) {
		this.brick = brick;
		this.currentRotation = 0;
		this.rotation = 0;
		this.posX = 0;
		this.posY = 0;
		this.matrixes = new ArrayList<boolean[][]>();
		this.matrixes.add(matrix1);
		this.matrixes.add(matrix4);
		this.matrixes.add(matrix3);
		this.matrixes.add(matrix2);
	}
	
	protected abstract int adjustXToFitCase();
	protected abstract int adjustYToFitCase();
	
	protected abstract int adjustXToFitCenter();
	protected abstract int adjustYToFitCenter();
	
	public void centerObject(int x, int y) {
		this.posX = x;
		this.posY = y;
		// reset object
		this.currentRotation = 0;
		this.rotation = 0;
		this.isMoving = false;
		this.isMovingDown = false;
	}

	public Image getBrick() {
		return this.brick;
	}

	private void updatePosition(Sens sens) {
		if(isMoving) {
			return ;
		}
		isMoving = true;
		if(sens == Sens.LEFT) {
		this.posX --;
		}
		else {
			this.posX ++;
		}

		new Timer() {
			@Override
			public void run() {
				isMoving = false;
			}
		}.schedule(TRANSLATION_PROTECTION_INTERVAL);
	}

	public void left() {
		updatePosition(Sens.LEFT);
	}

	public void right() {
		updatePosition(Sens.RIGHT);
	}

	public void down(final boolean check) {
		if(isMovingDown && check) {
			return ;
		}
		isMovingDown = true;
		this.posY ++;

		new Timer() {
			@Override
			public void run() {
				isMovingDown = false;
			}
		}.schedule(TRANSLATION_PROTECTION_INTERVAL);
	}
	
	/**
	 * Helper method to keep angles between -PI and PI
	 * @param angle the angle to adjust if needed
	 * @return the angle corrected
	 */
	private double adjustToMainCircle(double angle) {
		if(angle > 2) {
			return angle - 2;
		}
		if(angle < 0) {
			return angle + 2;
		}
		return angle;
	}

	/**
	 * Begins rotating in a way, setting the final angle to its destination
	 * @param sens whether to turn in the trigonometric way or not
	 */
	public void beginRotation(Sens sens) {
		if(sens == Sens.LEFT) {
			this.rotation += ANGLE;
		}
		else {
			this.rotation -= ANGLE;
		}
		this.rotation = adjustToMainCircle(this.rotation);
	}
	
	/**
	 * does a sub rotation, to do the whole rotation in the defined number of steps
	 * @param sens whether to turn in the trigonometric way or not
	 * @param nbSubRotate the number of steps needed to rotate
	 */
	public void rotate(Sens sens, int nbSubRotate) {
		if(sens == Sens.LEFT) {
			this.currentRotation += ANGLE / nbSubRotate;
		}
		else {
			this.currentRotation -= ANGLE / nbSubRotate;
		}
		this.currentRotation = adjustToMainCircle(this.currentRotation);
	}
	
	/**
	 * Ends the rotation, ensuring the angle is the desired one, in case of 
	 * lost of precisions during the calculation of sub rotations
	 */
	public void endRotation() {
		this.currentRotation = this.rotation;
	}
	
	public List<Point> previewRotateLeft() {
		return getPoints(this.matrixes.get((getRotationState() + 1) % 4));
	}
	
	public List<Point> previewRotateRight() {
		return getPoints(this.matrixes.get((getRotationState() + 3) % 4));
	}
	
	public void paint(Context2d ctx) {
		ctx.save();
		ctx.translate(this.posX * SQUARE_SIZE + this.adjustXToFitCase(), this.posY * SQUARE_SIZE - this.adjustYToFitCase());
		ctx.rotate(this.currentRotation * Math.PI);
		this.draw(ctx);
		ctx.restore();
	}
	
	public void paintAtLocation(Context2d ctx, int x, int y) {
		ctx.save();
		ctx.translate(x + this.adjustXToFitCenter(), y + this.adjustYToFitCenter());
		this.draw(ctx);
		ctx.restore();
	}

	/**
	 * Draws this piece around the 0,0 point
	 * @param ctx the context on which to write
	 */
	protected abstract void draw(Context2d ctx);

	public List<Point> getPoints() {
		return getPoints(this.matrixes.get(getRotationState()));
	}
	
	private List<Point> getPoints(boolean[][] matrix) {
		List<Point> points = new ArrayList<Point>();
		// coordinates of the center in the matrix
		int x = this.posX;
		int y = this.posY;

		for(int i = 0; i < matrix.length; i++) {
			for(int j = 0; j < matrix[i].length; j++) {
				if(matrix[i][j]) {
					Point point = new Point();
					point.x = x + j - 2;
					point.y = y + i - 2;
					points.add(point);
				}
			}
		}
		return points;
	}
	
	private int getRotationState() {
		int result = (int) Math.round(2 * this.rotation) % 4;
		return result;
	}
	
	/**
	 * Returns the center of this object
	 * @return the points corresponding to the center
	 */
	public Point getCenter() {
		Point point = new Point();
		point.x = this.posX;
		point.y = this.posY;
		return point;
	}
}
